{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bade6fb5",
   "metadata": {},
   "source": [
    "### 代码实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d17e5ec6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from torchinfo import summary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39cc4002",
   "metadata": {},
   "source": [
    "### 定义两种残差模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f3017c6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义第一种残差模块BasicBlock\n",
    "class BasicBlock(nn.Module):\n",
    "    # 设置expansion为1，用于计算最终输出特征图的通道数\n",
    "    expansion = 1\n",
    "\n",
    "    # 构造函数，接收输入通道数inplanes，输出通道数planes，步长stride和下采样层downsample\n",
    "    def __init__(self, inplanes, planes, stride=1, downsample=None):\n",
    "        super().__init__()\n",
    "        # 定义第一个卷积层，用3x3的卷积核对输入特征图进行卷积，输出通道数为planes，步长为stride，填充为1\n",
    "        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n",
    "        # BN层\n",
    "        self.bn1 = nn.BatchNorm2d(planes)\n",
    "        # 激活函数ReLU\n",
    "        self.relu = nn.ReLU(inplace=True)\n",
    "        # 定义第二个卷积层，用3x3的卷积核对输入特征图进行卷积，输出通道数为planes，步长默认为1，填充为1\n",
    "        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1, bias=False)\n",
    "        # BN层\n",
    "        self.bn2 = nn.BatchNorm2d(planes)\n",
    "        # 下采样层，用于调整输入x的维度\n",
    "        self.downsample = downsample\n",
    "        # 保存步长\n",
    "        self.stride = stride\n",
    "\n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 保存输入特征图\n",
    "        identity = x\n",
    "\n",
    "        # 卷积+BN+ReLU\n",
    "        out = self.conv1(x)\n",
    "        out = self.bn1(out)\n",
    "        out = self.relu(out)\n",
    "\n",
    "        # 卷积+BN\n",
    "        out = self.conv2(out)\n",
    "        out = self.bn2(out)\n",
    "\n",
    "        # 如果定义了下采样层，则调整输入x的维度\n",
    "        if self.downsample is not None:\n",
    "            identity = self.downsample(x)\n",
    "\n",
    "        # 将identity和out相加，并使用ReLU激活函数激活\n",
    "        out += identity\n",
    "        out = self.relu(out)\n",
    "\n",
    "        # 返回输出特征图\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "73730002",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义第二种残差模块Bottleneck\n",
    "class Bottleneck(nn.Module):\n",
    "    # 设置expansion为4，用于计算最终输出特征图的通道数\n",
    "    expansion = 4\n",
    "\n",
    "    # 构造函数，接收输入通道数inplanes，输出通道数planes，步长stride和下采样层downsample\n",
    "    def __init__(self, inplanes, planes, stride=1, downsample=None):\n",
    "        super().__init__()\n",
    "        # 定义第一个卷积层，用1x1的卷积核对输入特征图进行卷积，输出通道数为planes\n",
    "        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)\n",
    "        # BN层\n",
    "        self.bn1 = nn.BatchNorm2d(planes)\n",
    "        # 定义第二个卷积层，用3x3的卷积核对第一个卷积层的输出进行卷积，输出通道数为planes，步长为stride，填充为1\n",
    "        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n",
    "        # BN层\n",
    "        self.bn2 = nn.BatchNorm2d(planes)\n",
    "        # 定义第三个卷积层，用1x1的卷积核对第二个卷积层的输出进行卷积，输出通道数为planes * 4\n",
    "        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)\n",
    "        # BN层\n",
    "        self.bn3 = nn.BatchNorm2d(planes * 4)\n",
    "        # 激活函数ReLU\n",
    "        self.relu = nn.ReLU(inplace=True)\n",
    "        # 下采样层，用于调整输入x的维度\n",
    "        self.downsample = downsample\n",
    "        # 保存步长\n",
    "        self.stride = stride\n",
    "\n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 保存输入特征图\n",
    "        identity = x\n",
    "\n",
    "        # 卷积+BN+ReLU\n",
    "        out = self.conv1(x)\n",
    "        out = self.bn1(out)\n",
    "        out = self.relu(out)\n",
    "\n",
    "        # 卷积+BN+ReLU\n",
    "        out = self.conv2(out)\n",
    "        out = self.bn2(out)\n",
    "        out = self.relu(out)\n",
    "\n",
    "        # 卷积+BN\n",
    "        out = self.conv3(out)\n",
    "        out = self.bn3(out)\n",
    "\n",
    "        # 如果定义了下采样层，则调整输入x的维度\n",
    "        if self.downsample is not None:\n",
    "            identity = self.downsample(x)\n",
    "\n",
    "        # 将identity和out相加，并使用ReLU激活函数激活\n",
    "        out += identity\n",
    "        out = self.relu(out)\n",
    "\n",
    "        # 返回输出特征图\n",
    "        return out"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d781d405",
   "metadata": {},
   "source": [
    "### 结构定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e51e4d75",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义ResNet的网络结构\n",
    "class ResNet(nn.Module):\n",
    "\n",
    "    # 构造函数，接收残差块类型block、残差块数量列表layers和类别数num_classes\n",
    "    def __init__(self, block, layers, num_classes=1000):\n",
    "        super().__init__()\n",
    "        # 定义第一个卷积层，用7x7的卷积核对输入特征图进行卷积，输出通道数为64，步长为2，填充为3\n",
    "        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)\n",
    "        # BN层\n",
    "        self.bn1 = nn.BatchNorm2d(64)\n",
    "        # 激活函数ReLU\n",
    "        self.relu = nn.ReLU(inplace=True)\n",
    "        # 定义3x3最大池化层对特征图进行池化，步长为2，填充为1\n",
    "        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n",
    "        # 初始化输入通道数inplanes为64\n",
    "        self.inplanes = 64\n",
    "        # 定义layer1，使用_make_layer函数生成一个layer，通道数64，包含layers[0]个block\n",
    "        self.layer1 = self._make_layer(block, 64, layers[0])\n",
    "        # 定义layer2，使用_make_layer函数生成一个layer，通道数128，包含layers[1]个block，步长为2\n",
    "        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)\n",
    "        # 定义layer3，使用_make_layer函数生成一个layer，通道数256，包含layers[2]个block，步长为2\n",
    "        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)\n",
    "        # 定义layer4，使用_make_layer函数生成一个layer，通道数512，包含layers[3]个block，步长为2\n",
    "        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)\n",
    "        # 平均池化层，输出大小为channel*1*1\n",
    "        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n",
    "        # 定义全连接层，将输入维度设置为512 * block.expansion，输出维度设置为num_classes\n",
    "        self.fc = nn.Linear(512 * block.expansion, num_classes)\n",
    "\n",
    "    # 生成网络结构的函数，根据传入的配置，拼接出对应的网络结构\n",
    "    def _make_layer(self, block, planes, blocks, stride=1):\n",
    "        # 下采样层一开始为None，用于调整输入的维度\n",
    "        downsample = None\n",
    "        # 如果步长不为1或者输入通道数与输出通道数不一致，则需要对输入特征进行调整\n",
    "        if stride != 1 or self.inplanes != planes * block.expansion:\n",
    "            # 定义下采样层，包括1x1卷积和BN层\n",
    "            downsample = nn.Sequential(\n",
    "                nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False),\n",
    "                nn.BatchNorm2d(planes * block.expansion),\n",
    "            )\n",
    "\n",
    "        # 定义一个layers列表\n",
    "        layers = []\n",
    "        # 将第一个block添加到layers列表中\n",
    "        layers.append(block(self.inplanes, planes, stride, downsample))\n",
    "        # 更新inplanes为下一个基本块的输入通道数\n",
    "        self.inplanes = planes * block.expansion\n",
    "        # 添加剩余的基本块到layers列表中\n",
    "        for i in range(1, blocks):\n",
    "            layers.append(block(self.inplanes, planes))\n",
    "\n",
    "        # 返回所有的block\n",
    "        return nn.Sequential(*layers)\n",
    "\n",
    "    # 定义前向传播函数\n",
    "    def forward(self, x):\n",
    "        # 第一部分，7x7卷积+BN+ReLU+最大池化层\n",
    "        x = self.conv1(x)\n",
    "        x = self.bn1(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.maxpool(x)\n",
    "\n",
    "        # 第二部分，4组残差模块\n",
    "        x = self.layer1(x)\n",
    "        x = self.layer2(x)\n",
    "        x = self.layer3(x)\n",
    "        x = self.layer4(x)\n",
    "\n",
    "        # 第三部分，平均池化+全连接层\n",
    "        x = self.avgpool(x)\n",
    "        x = torch.flatten(x, 1)\n",
    "        x = self.fc(x)\n",
    "\n",
    "        # 输出\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "915d0da2",
   "metadata": {},
   "source": [
    "### 封装函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6eb0680e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 封装函数对应5个模型，num_classes表示类别数\n",
    "# 其中数值与网络结构表格中的数值完全一致，可参考论文结构表\n",
    "def resnet18(num_classes=1000):\n",
    "    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes=num_classes)\n",
    "\n",
    "def resnet34(num_classes=1000):\n",
    "    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes)\n",
    "\n",
    "def resnet50(num_classes=1000):\n",
    "    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes)\n",
    "\n",
    "def resnet101(num_classes=1000):\n",
    "    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes)\n",
    "\n",
    "def resnet152(num_classes=1000):\n",
    "    return ResNet(Bottleneck, [3, 8, 36, 3], num_classes=num_classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdec4fd3",
   "metadata": {},
   "source": [
    "### 网络结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3a35d422",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "==========================================================================================\n",
       "Layer (type:depth-idx)                   Output Shape              Param #\n",
       "==========================================================================================\n",
       "ResNet                                   [1, 1000]                 --\n",
       "├─Conv2d: 1-1                            [1, 64, 112, 112]         9,408\n",
       "├─BatchNorm2d: 1-2                       [1, 64, 112, 112]         128\n",
       "├─ReLU: 1-3                              [1, 64, 112, 112]         --\n",
       "├─MaxPool2d: 1-4                         [1, 64, 56, 56]           --\n",
       "├─Sequential: 1-5                        [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-1                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-1                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-2             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-3                    [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-4                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-5             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-6                    [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-2                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-7                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-8             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-9                    [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-10                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-11            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-12                   [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-3                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-13                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-14            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-15                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-16                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-17            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-18                   [1, 64, 56, 56]           --\n",
       "├─Sequential: 1-6                        [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-4                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-19                 [1, 128, 28, 28]          73,728\n",
       "│    │    └─BatchNorm2d: 3-20            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-21                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-22                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-23            [1, 128, 28, 28]          256\n",
       "│    │    └─Sequential: 3-24             [1, 128, 28, 28]          8,448\n",
       "│    │    └─ReLU: 3-25                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-5                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-26                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-27            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-28                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-29                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-30            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-31                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-6                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-32                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-33            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-34                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-35                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-36            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-37                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-7                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-38                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-39            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-40                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-41                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-42            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-43                   [1, 128, 28, 28]          --\n",
       "├─Sequential: 1-7                        [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-8                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-44                 [1, 256, 14, 14]          294,912\n",
       "│    │    └─BatchNorm2d: 3-45            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-46                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-47                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-48            [1, 256, 14, 14]          512\n",
       "│    │    └─Sequential: 3-49             [1, 256, 14, 14]          33,280\n",
       "│    │    └─ReLU: 3-50                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-9                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-51                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-52            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-53                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-54                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-55            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-56                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-10                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-57                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-58            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-59                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-60                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-61            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-62                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-11                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-63                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-64            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-65                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-66                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-67            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-68                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-12                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-69                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-70            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-71                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-72                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-73            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-74                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-13                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-75                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-76            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-77                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-78                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-79            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-80                   [1, 256, 14, 14]          --\n",
       "├─Sequential: 1-8                        [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-14                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-81                 [1, 512, 7, 7]            1,179,648\n",
       "│    │    └─BatchNorm2d: 3-82            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-83                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-84                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-85            [1, 512, 7, 7]            1,024\n",
       "│    │    └─Sequential: 3-86             [1, 512, 7, 7]            132,096\n",
       "│    │    └─ReLU: 3-87                   [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-15                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-88                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-89            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-90                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-91                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-92            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-93                   [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-16                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-94                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-95            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-96                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-97                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-98            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-99                   [1, 512, 7, 7]            --\n",
       "├─AdaptiveAvgPool2d: 1-9                 [1, 512, 1, 1]            --\n",
       "├─Linear: 1-10                           [1, 1000]                 513,000\n",
       "==========================================================================================\n",
       "Total params: 21,797,672\n",
       "Trainable params: 21,797,672\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 3.66\n",
       "==========================================================================================\n",
       "Input size (MB): 0.60\n",
       "Forward/backward pass size (MB): 59.82\n",
       "Params size (MB): 87.19\n",
       "Estimated Total Size (MB): 147.61\n",
       "=========================================================================================="
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看模型结构及参数量，input_size表示示例输入数据的维度信息\n",
    "summary(resnet34(), input_size=(1, 3, 224, 224))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4514065e",
   "metadata": {},
   "source": [
    "### torchvision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "9b5a2216",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "==========================================================================================\n",
       "Layer (type:depth-idx)                   Output Shape              Param #\n",
       "==========================================================================================\n",
       "ResNet                                   [1, 1000]                 --\n",
       "├─Conv2d: 1-1                            [1, 64, 112, 112]         9,408\n",
       "├─BatchNorm2d: 1-2                       [1, 64, 112, 112]         128\n",
       "├─ReLU: 1-3                              [1, 64, 112, 112]         --\n",
       "├─MaxPool2d: 1-4                         [1, 64, 56, 56]           --\n",
       "├─Sequential: 1-5                        [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-1                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-1                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-2             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-3                    [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-4                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-5             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-6                    [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-2                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-7                  [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-8             [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-9                    [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-10                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-11            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-12                   [1, 64, 56, 56]           --\n",
       "│    └─BasicBlock: 2-3                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-13                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-14            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-15                   [1, 64, 56, 56]           --\n",
       "│    │    └─Conv2d: 3-16                 [1, 64, 56, 56]           36,864\n",
       "│    │    └─BatchNorm2d: 3-17            [1, 64, 56, 56]           128\n",
       "│    │    └─ReLU: 3-18                   [1, 64, 56, 56]           --\n",
       "├─Sequential: 1-6                        [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-4                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-19                 [1, 128, 28, 28]          73,728\n",
       "│    │    └─BatchNorm2d: 3-20            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-21                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-22                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-23            [1, 128, 28, 28]          256\n",
       "│    │    └─Sequential: 3-24             [1, 128, 28, 28]          8,448\n",
       "│    │    └─ReLU: 3-25                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-5                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-26                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-27            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-28                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-29                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-30            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-31                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-6                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-32                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-33            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-34                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-35                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-36            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-37                   [1, 128, 28, 28]          --\n",
       "│    └─BasicBlock: 2-7                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-38                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-39            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-40                   [1, 128, 28, 28]          --\n",
       "│    │    └─Conv2d: 3-41                 [1, 128, 28, 28]          147,456\n",
       "│    │    └─BatchNorm2d: 3-42            [1, 128, 28, 28]          256\n",
       "│    │    └─ReLU: 3-43                   [1, 128, 28, 28]          --\n",
       "├─Sequential: 1-7                        [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-8                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-44                 [1, 256, 14, 14]          294,912\n",
       "│    │    └─BatchNorm2d: 3-45            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-46                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-47                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-48            [1, 256, 14, 14]          512\n",
       "│    │    └─Sequential: 3-49             [1, 256, 14, 14]          33,280\n",
       "│    │    └─ReLU: 3-50                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-9                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-51                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-52            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-53                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-54                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-55            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-56                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-10                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-57                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-58            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-59                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-60                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-61            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-62                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-11                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-63                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-64            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-65                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-66                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-67            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-68                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-12                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-69                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-70            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-71                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-72                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-73            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-74                   [1, 256, 14, 14]          --\n",
       "│    └─BasicBlock: 2-13                  [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-75                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-76            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-77                   [1, 256, 14, 14]          --\n",
       "│    │    └─Conv2d: 3-78                 [1, 256, 14, 14]          589,824\n",
       "│    │    └─BatchNorm2d: 3-79            [1, 256, 14, 14]          512\n",
       "│    │    └─ReLU: 3-80                   [1, 256, 14, 14]          --\n",
       "├─Sequential: 1-8                        [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-14                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-81                 [1, 512, 7, 7]            1,179,648\n",
       "│    │    └─BatchNorm2d: 3-82            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-83                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-84                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-85            [1, 512, 7, 7]            1,024\n",
       "│    │    └─Sequential: 3-86             [1, 512, 7, 7]            132,096\n",
       "│    │    └─ReLU: 3-87                   [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-15                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-88                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-89            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-90                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-91                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-92            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-93                   [1, 512, 7, 7]            --\n",
       "│    └─BasicBlock: 2-16                  [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-94                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-95            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-96                   [1, 512, 7, 7]            --\n",
       "│    │    └─Conv2d: 3-97                 [1, 512, 7, 7]            2,359,296\n",
       "│    │    └─BatchNorm2d: 3-98            [1, 512, 7, 7]            1,024\n",
       "│    │    └─ReLU: 3-99                   [1, 512, 7, 7]            --\n",
       "├─AdaptiveAvgPool2d: 1-9                 [1, 512, 1, 1]            --\n",
       "├─Linear: 1-10                           [1, 1000]                 513,000\n",
       "==========================================================================================\n",
       "Total params: 21,797,672\n",
       "Trainable params: 21,797,672\n",
       "Non-trainable params: 0\n",
       "Total mult-adds (G): 3.66\n",
       "==========================================================================================\n",
       "Input size (MB): 0.60\n",
       "Forward/backward pass size (MB): 59.82\n",
       "Params size (MB): 87.19\n",
       "Estimated Total Size (MB): 147.61\n",
       "=========================================================================================="
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看torchvision自带的模型结构及参数量\n",
    "from torchvision import models\n",
    "summary(models.resnet34(), input_size=(1, 3, 224, 224))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5190c45e",
   "metadata": {},
   "source": [
    "### 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "bec77b2f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 0 Loss: 2.623599899761186 Acc: 0.03627450980392157\n",
      "Epoch: 10 Loss: 2.3830474543312303 Acc: 0.26176470588235295\n",
      "Epoch: 20 Loss: 2.2793586847355334 Acc: 0.35784313725490197\n",
      "Epoch: 30 Loss: 2.2096644109618815 Acc: 0.4411764705882353\n",
      "Epoch: 40 Loss: 2.1320964186829308 Acc: 0.4803921568627451\n",
      "Epoch: 50 Loss: 2.0661568830917143 Acc: 0.48823529411764705\n",
      "Epoch: 60 Loss: 2.006702798099011 Acc: 0.5754901960784313\n",
      "Epoch: 70 Loss: 1.9476898188148646 Acc: 0.596078431372549\n",
      "Epoch: 80 Loss: 1.8956279669610685 Acc: 0.5852941176470589\n",
      "Epoch: 90 Loss: 1.833168720418988 Acc: 0.6549019607843137\n",
      "Epoch: 100 Loss: 1.7558072815668513 Acc: 0.6549019607843137\n",
      "Epoch: 110 Loss: 1.7468234069683082 Acc: 0.6617647058823529\n",
      "Epoch: 120 Loss: 1.719101311330019 Acc: 0.6696078431372549\n",
      "Epoch: 130 Loss: 1.7125622612866465 Acc: 0.6666666666666666\n",
      "Epoch: 140 Loss: 1.6611204454679491 Acc: 0.6784313725490196\n",
      "Epoch: 150 Loss: 1.5911555289413652 Acc: 0.6803921568627451\n",
      "Epoch: 160 Loss: 1.587745685309724 Acc: 0.6941176470588235\n",
      "Epoch: 170 Loss: 1.5421918464217708 Acc: 0.7215686274509804\n",
      "Epoch: 180 Loss: 1.4948699055832393 Acc: 0.7       \n",
      "Epoch: 190 Loss: 1.5106950314087813 Acc: 0.7107843137254902\n",
      "100%|██████████| 200/200 [1:15:03<00:00, 22.52s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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EREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOVeJgZMmSJdx2222EhYVhs9mYOXPmJfdZvHgxEREReHh4UK9ePT777LPSlFVERESuQSUORlJSUmjdujUff/xxsbaPioqiX79+dOnShY0bN/Liiy8yfPhwpk2bVuLCioiIyLXHpaQ79O3bl759+xZ7+88++4xatWrxwQcfANC0aVPWrVvHe++9x913313S04uIiMg15or3GVm5ciW9e/fOt6xPnz6sW7eOzMzMQvdJT08nOTk530tERESuTVc8GImLiyM4ODjfsuDgYLKysoiPjy90n7Fjx+Lv75/7Cg8Pv9LFFBEREQe5KqNpbDZbvs+WZRW6PMeYMWNISkrKfcXExFzxMoqIiIhjlLjPSEmFhIQQFxeXb9nx48dxcXEhMDCw0H3c3d1xd3e/0kUTERGRcuCK14x07NiR+fPn51s2b948IiMjcXV1vdKnFxERkXKuxMHImTNn2LRpE5s2bQLM0N1NmzYRHR0NmCaWAQMG5G4/ZMgQDh06xMiRI9m5cydfffUVEyZMYNSoUWVzBSIiIlKhlbiZZt26dfTo0SP388iRIwEYOHAgX3/9NbGxsbmBCUDdunWZM2cOzz77LJ988glhYWF8+OGHGtYrIiIiANisnN6k5VhycjL+/v4kJSXh5+fn6OKIiIhIMRT3+a25aURERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBEREREHErBiIiIiDiUghERERFxKAUjIiIi4lAKRkRERMShFIyIiIiIQykYEREREYdSMCIiIiIOpWBERESuPWcTYfmHkJpw8W1WjoPE6KtVqvIldgtELQXLcnRJFIyIiMg1aOUnMP8V+G104evt2fDDAPh9DHx9C6ScvDLlSEuG3b9BVkbessyzkH6m9Mc8cxyOboKoJRC/L/+6s4n5g4sNk+D7R+D4rvzbnY6DCb3hm1vh23sgIar05SkDLg49u4iIlH/7/oCjG6HTcHBxvzLnSE0wD8jqTcCpDL4nx201/+78BdJPg7tv/vUL34KoxeZ9YjT8OBD6vgtrv4QTu8DNB7yrQYMboVEfcPMueRkOr4efHoXEQ9B+CPR9xwQln90AZ0/Bo3OheqPC9z19DGI3mbKfPQVJh81xjqwvWJNz0z+g09Ow/AP4802o2w0emAoxq2DWcMCCvfPN+dsOAJsNVn0KWWfN/vvmw7gOcMv70Oahkl9nGbBZVjmon7mE5ORk/P39SUpKws/Pz9HFEREpe4nRMG0wXPcQRAx0dGny7J4LUx8Ayw7dx0D3F8y3+pWfQJ0boE7ni+9vWXBkA9iAsLbmQXghux0+7WiCAK9AqNcdQlpBUFOoUhv8Qs03931/mAd00hFIS4SgZlCjLSTHmod0cHO4/SNzjg/bQMIBc/z+n8J1D+adb+cv8P3D5n33MaY5JzOl6Gtw9YKeL0PHoZf+eWWkwIFFsOd32PQt2LPMcjdfeG4X7JkL0x43y6rWgcELwDswb//sTPOzXfwOZKYWcRIb+IaAq2feNYa0grgteZs0uMl8PnMMvIMg5bhZ3vkZuGEk/KcFZJyGPmNhz2+mluWx36FWh0tfYwkU9/mtYEREpCQOLjfV7A1vNJ/TT8O26dDkFvNN+lL2/A7e1c1D9Hy/PgvrvgJXbxixpXjHAvjzH7B5Ktz3v4LHLK30M+a64vfAlPvyvkE7u8FTK2D+q7B7jvn8yAwTlFzIng1rJ5iahvjdZlm9HtDnXyZoON/+hfC//mVT9mc2g08IvBVqAigwNQUDZ5n3a74wTTdWNlz/V+j37nnBiQ0a94Pm/SErDU7ug+0zTY0EwE1vmtqhuK1waAWc2AlnTkCPFyGkBSQfhS9vguTDeeVpfifEbjZBw23/hU1TIGY12JxM+WpeD+0eBw9/iF5lypKw3+wb2AD8wsDdD/xqgH8NE3TUiACPc8/CFR/BvJfNe5sztP+r+T3KSjPLqjeFJ/6E1Z/Dn2+YZTUi4cg6s+6pFSZ4O7wOwtuVzT04j4IREZHLlZYEO342DxR3X9Ov4P2mkJ0Od31hlk++y3yrvO5h6P/JxY93cDl83Q/c/c23ZDcvs/xsojluzjfhTsOh9z8uXb4Tu031umUH/3D46xLwCshbv+YLOB1rvv07u5pl9mw4vuNcTUILqBmZ/5i7ZsP0JyHjvD4NDW4y3/APLASPKqZWIoe7Pzw41Sx3djUP0Kx0mPGk+dmBqVmwZ0F2hnlg3j8FGt+cd4yfHoNt06DtQGh9v/k5ndhpri8pxtwHNx8TVNTpbGpL3H3MQ/7oJlNLsHsOnDoI93xjyvBZZ3DxOPdQtsHQ1aZpYv1Ec87WD8LtH+b9XOK2maaYgLr5fx6WBUveg4X/NJ/9a0HSBc0kPsHw6G8w82+macQnBJrdDo37mgBs5ccmYPANNffDyRUenmYCoPTkgvfVMwB6/9PU5hRWk3ShbdNNAHLDCNOstOd3mPqgCRafWAhBTcx2S9/PC0gA7vzc/LyvIAUjIiKXIysDJt0O0Ssh8jG49T/mm/7skWa9s5tpTtg7z3z2qgaj9ub1d7DbTVt87BZo/6T55vvNbSZwARPMtLrXvF85znSkdPeH9CRw8TTf8H2DzfqYNfDH6xDWBprfZWpAbDaY+hDs+jWvzPV7wUM/gpNz/rK2HWi+lW+bBnNGmT4IAM7u52o2zjW1HNsBX95omixszuYc9XvCXyaa6v5xHU0gBqbpY8Mk8/M5X0A9E7jFbjYP3ZvehDYPQ+pJmPuCaabwCjTfyH1DTF+Rfzcxx31ykbnGC6WfMX1VcgKHwswaDhu+gRueNUHWtMdNrYOTsymjkyvYM822N74OnUcU70GfY9HbsGisee/iAXW7QkhL0zn1+A5zz7LOmlqMJxdBYP28fc8PYgFa/AX+MsEEUmvGm4ArJd4cr8GN0LA3eFYpftkKk3AAnFygSq28ZZZl7v/aL01QNXzDxX+mZUDBiIiUDyf3m2/GfqFle9xj200TRcRA8w0UTAAQv8dUgycfNcFC+PWmGWDD1+Yh1f2F4h0/p9kETNPJczvhuwfg0HLzME09N/rC5mQedNnp5ltojbawZx7Me8mUBcyDq+vfTTCSo14PGDDTlPnjCPPwuOV9U41/ZB10+BvcPNY8QMZ3Mw/3HKHXQesHYO5oc/67J5hv5VlnzXGb3WEeOjn9FcAEFfsXmPduvqYZ6FSUCZIG/moe2lMfNLULdbvCwzPA+YIxDss/NCNUer0KXZ4zgcQPA8zP293P1KbkNA+4+cL930K9bnn7Z6XDl71MM0f9nvDQNFj7Bfz2PAS3hCFLSxYgnG/dV+ae1eth7vnid6DNI6ZJ49cRZpuqdeHW9825S2PHz6ZPR6M+eR1iL2yauX+KabK70LQnYOsP5v3j800ZHcGeDVt+MEFfTo3JFaRgREQcL24bfNHDVDs/vd5UrZdEVgYse998Yzz/P/j0M/B5F/MAtznDPRPNN8CZQ+H49vzHcPXK3xHw0blQu6N5n5qQ16xhzzbV5se2m2Dj6AbAZh7aKSfMN+nl/wUsGLrWNEMc3Qg3vw0Hl5kaiu4vQuSjpnNgdrp5QNuzTU1DTjnq94L9f5pjP7vdfKv+9i+mVmTkDvNgn3yXqXkZsgySj8D/7jT7N7rZ1Cycfz1tB5hOm1t/Ms0rVnbeuuZ3mWaY31/MW9ZxGNz4hqklmHSHOd/5qtSCJxbl71R5vrQkE8AUJv2MKV/0KvNzuLBvCJiml8+7mqAlvL3pjJp8GPr+n6lBKq2jG2F8d/CsaoKpHT+bpo52g831+4WZa3f1LP05inJ8l6mFano7dBhS+DaH18OEm0wQ8uhvpQ+6KhgFIyJSdmK3mB7+8btNO3P1xpfex26Hr/rA4TXm803/gM7DC24XvQoWv2v6E7S671z/jHNBy8pP8h6kPV8x38ZtNvh5GGz8n6mGtmeZgATMg9jF03wb9gmCfX+aZg83XwioY76R17weHp9nvi2v/xr+8hW0uNsEFF9f8I2216smoJgzKm9ZrU7w2G/mG3JitKmOX/8N/DIcarYz/RqWvmdGjgz42XR0zBmN4uQCT2+AGUMgeoUZObN3ngl2OgyFm98yNSFT7jXLa7YzTQIHl0L7p6Dv26bD5KK3TNndfExfCL8wU7aEA6Z/w+bvILQ1DJptgpj5r5qf141v5B+pk5oAE/ueG8rqC2HXQb//M6NYrqT138AvzwDnHj/O7jBqtwkkSisrHd6qYYKsnH4tD/0EDW8qgwKXkZP7Tedlj8rzHFMwIiJl45dnzIMvR1gbePyPglX4F8p5QOfwrg7PbDHfxFd9aoKKzLN5uR5yeAbAwF+gWkP4b2vT4S9H/V4mUNnxM2AzD/tN38KW78365ndCv3/nfavPSodj20yHxsyzZrhnvtoJoFZHeGyuGWGx+jNo1Bda3WNqJprcakaV/LtJ3tDPW/5tvm2fL+kI/KeZKZO7nwmA7v2f6cQIsOoz06Ry/ZPmYb9hEsx6Om//kJYwYFZeLU3SEdMxNadzo5MLDN8EVcLPO+e5ZgH/mgV/9mcTTRDi4pa3zLIK/zaemQZnE0znyqv5bf3kfhMAHt1oRuO0/MvlH/Pzrvmbs57ZAlVrX/5xpdSK+/xW0jMRKdrJ/ecCEZt50O//0zw8Vn5kOgoW5cxx+OM18/7GN0x7fuIh+HGQyRVxflOCzcm07QfUNec6ddBsFzHIBCJ+NUz/iXkv5wUQYEYO1OtmHmQ125nRJOeP0ADT6bFGhHnv4W+Os/S9/MeJXgmnDplRJGCaPZr0y1vv4Qet7zPXYHOGZv0LXq9/DdMf5dg2E4gE1MvfrNRhiBku6l3dfG7WH+Y8b/p41IiEh3/KXyvgX8OMpvnlGfO55b35AxEoPAjJUVjnx6ICDVcPcA0r+lhXSmB98yrLnCqh1+UFI65e5ndCKgQFIyLXOssy/RYKq8k4tNIEA7XaF77v7jnm37pdTb+MTVNg5lOwcKxprihsv9QE08fh7CnzgO44zHzjn/U07P3dbNP8LqjfAzJSTUCR0yzQZoDJTnlyr+kACmaYa4chJuCIWWWWeVY1QzPBdLy8/oni/Sw6DzdBxdkE6DbaNKEcXGoCnaQY01G1fo+C+3X4G2ydZmo6isr/0eBGE4yAuWYn5/zrfUPy3nv4mdEtR9ZDr1cKZgcFMwJm73wz+qbLyOJdX2UX1saMqAFTs1YWmVzlqlAzjci17vuHTXX4wF9Mc0COk/vh43bmofnMlrzRLklHTB8Emw2+6mv6NvR91yRTsiwzj8W++WbbsDamhqTZHeZzaoIZDhu31WR9fHSOeShkZcC49qZPQ6fhprakqAfFoRWm74ZlN8NlR2zNy8dRFuK2QvxeU9Oz8X/5m0ua3QH3Tip8P7v94g+36NXwVW9T5me3lU1HSbvd/Bwu1SQmRk4nVjD9j+4a79DiSPGf3wobRRwpLQli1pqHTklc+B3CsvJyR5wvaonJ6Hj2lGn6OH9yrhUfmeaS7AzzUAbTl+M/zUxOhZSTeTUROUNnbTaTX6L5XWY469GNZmjnzKGw+XvTzyFuq2mOGPSrCUTA9F14bJ7Jv9D7Hxd/qNfuZIIVMMNwyzIQAROQtbjLXEvT203nyRxNbit6v0t9y67V3vQTGfBz2Y3YcHJSIFISQc3M7yVAtSLmfJFyScGIiKPE7zVNEhNuNLUG6yZCdtal9zt1CD68Dib2M3NypJ8xKbvfrQcb/pe3nWWZycBynNxnhh9alpmQbNO3eevWf2NqNXKSOi19D1b813wrD26ZP3GST3XTZPPcLjPHhc0JNk02Q13PHIOA+qYW5sIRNz7VC09oVZjOw+GFmOI3v5SWZxWTMwLMQ6xR78s7XrPbTVpwcQwX97zfsdDrHFoUKRmF3CKlEbPWpIRucXfJ9ju2w0xYdfYU/DrS9F0Akxzr1xFmiGXfd4rePzvL5JI4ddC8vuhphrDGbjLrf3ve1CwE1jepu6NXmm/+d35qJmHb8v25/gk2UyMS1tYcJ/mwCWjSksxx7FnncmqQvzPn+byrwY2vQYNeJqFT6kkz9LbzM6ZT5OW6WsMfIwbBzlmmw2lR+TOk4ug/Dg6vNb+XUmGoz4hISSUcgE87myGiJZnlsrA8FmFtTVro7TPNnBE2Z/jbKjOtePoZMwIloF5etf/id2Hhv0xOCL/QvAyfngGm9iJ2k0kk1Wcs/PqMaTLJyeS5/L8m38T57v/OZBRd+XHespvfMWXJSaxVVIru82Wlm+0vJ0+EIx3faUZelDQpm4hclIb2ipSV7ExTe1Cltsn3MGt43oN6yw9FByMZKaamISch1ZovzL8+ISbDZ422Jnunu48ZLXF4rRm9Mv9VM2x1yn0mcZPNCXzDTN+BxHMTdN3ynunHMetp02xz95cmL8annU0ejy/Ppbt29TKZQ8HUWIS0gtnPmVlBg5qZjJ7VGuUFI2FtTUfVrDQzNNevZvGqu13czauiutJJvkTkolQzInK+nJTgh9dCtcamSePQcpN8yq8m1O1isltiAywTVDy3u+BkU3Y7TLwZjmwwHTkDG8K/G5vskH9dYrJjXujEnnMzsGabwCI7I+/f87W8x0yyVljeiJyht65epi9Ex6ehZkT+bTLTTNAT3t7kswD47kEz7PaRmeYas7PMnCE1Ihw3h4aIVHjKwCpSGjmZMi9kczKdOXP0ectMx50abyb7anhj/u23z4QfzyVzCm5pkmbNe9nUTAxZWvT5Z48yQQCYTKB/+coEQokxgGVqZkJaXXyERUKU6Ufi5l2cKzYyz5p+LDm1OCIiZUDNNFJ5LX7XTOt93+S8b/7FkRgDf75p3ncfY5plUk+adOFBTUwK71WfmqGh7Z8yD/21X8DWH/MHI9lZpl9HjmNb4c9zfTvaDrh4GXq8aJpigpqauVicXczQ1vMTZl1KQN3ib5vD1fPKTCAmIlIMqhmRa0tWBrxb10xl3uRWM4V5gW3STY6N2E2mz4Z/TTPc9bv7zYyjtTrCoDmXziuRk+TKzQfaPW6yZQY3N0HM0vdMp9JOT5vOoFA2k4GJiFQgqhmRyunwGhOIgJnSfc/veXkkAA4uN5O3ndxnPsfvNdN5L3nPBCLObiZNd3HSSIdfD/61zBDfnGGwx3fkrb/hWeg4FLZPN6Namt2uQEREpBBKeibXlv0LzL8u5/JczBll5j8BOLweJt1hAhGfYDOq5cQukxJ91Sdmm77vFEzWVRSbzSTncnaDhr1NZtKW95p+HQH1TcIuJ2f4y0SIeBRufL1ML1VE5FqhZhqpGJKPmplgPfzMMNeikmqN725SlPd7D5Z9YJJ51e8Jff/PBCLJh6FhHzNnRdJhmNjXdBC1OcHtH0Gbhy+/rGcTTUCinBUiUsld0blpxo0bR926dfHw8CAiIoKlS4seHbBo0SJsNluB165du0pzaqkMTh8z85zkpEZPOAAfRcD4bvBhG/M6ub/gfikn4egm877pbXDHx+DiaWpLPmlnApGA+iYnh2cVk7b7wR+gbje45+uyCUTAHFuBiIhIsZU4GPn+++8ZMWIEL730Ehs3bqRLly707duX6Ojoi+63e/duYmNjc18NGzYsdaGlgshIhbljzMiW4rIsM/HajCdh0bl5VZb+2yQZc/UyzS+nj8LPwwpOLhe1CLAgqLkZfVK/BwyeD1XrmGG5rl5mhM35acZrd4SBs/JmnRURkauuxMHI+++/z+OPP87gwYNp2rQpH3zwAeHh4Xz66acX3S8oKIiQkJDcl7Ozc6kLLRXE6s9g1Tgzl0rquTlYEmNMJtLV42HjZEg/nX+fqMV5M8Wu+MjUamyeaj4PmAXD1prRK9Er8vJx5MjpL1K/R96ykJYmnXm30fDwNAhuVuaXKSIil6dEwUhGRgbr16+nd+/8M1v27t2bFStWXHTfNm3aEBoaSq9evVi4cOFFt01PTyc5OTnfSyqYjFRYea5TaHqyeZ9+BibdbjqV/vZ3+HkofHN7XgdTy4JF5yaJy8k8+u29ZtK2+j0hvJ2ZfyWnI+gfr5sMpwBpybAvJxjpmb8snlVN/o7ana7kFYuISCmVKBiJj48nOzub4ODgfMuDg4OJi4srdJ/Q0FDGjx/PtGnTmD59Oo0bN6ZXr14sWbKkyPOMHTsWf3//3Fd4eHhJiinlwYZvTHZSVy/zefVn8PPfTP8Pn2BofqcJEo5ugJlDTJPLwWWmxsPZDR76yUzpbs80+3c7Lytq5ONQp4tpuvmyF8z8G3xyvWm+cfdT0CEiUsGUKs+I7YI5MSzLKrAsR+PGjWncOG+oZMeOHYmJieG9996ja9euhe4zZswYRo4cmfs5OTlZAUlFkpUOyz8073v/E9Z/DXFbYMfPgM0Mda3T2eT8mHSHWT6uA6ScMPu0HQD1upkcHcs/MB1Mz5+MzskJ7vnG1LBsnw6bziU2C6gHd3yiTKIiIhVMiYKRatWq4ezsXKAW5Pjx4wVqSy6mQ4cOTJ48ucj17u7uuLtX4BlAK7tNU0wthW+oGaHiVwO+u8+s6zLSBCJg/r39I1MzEr/bLHPzMcnCwKRDD2kJ9boXPId3INwzEdo8ZIbw1u5s9itqyK+IiJRbJQpG3NzciIiIYP78+dx55525y+fPn88ddxR/NMLGjRsJDQ0tyanFkSzL9O84uNRMOd/iL1CrvVmXmQaT7zZBwP1TzKy3i8/1++g03Ewr36gPtBsMGSlmzpfzXfeAmcE2MdrMkBtYP28eFmcXaPmXi5etwY3mJSIiFVaJm2lGjhzJI488QmRkJB07dmT8+PFER0czZMgQwDSxHDlyhEmTJgHwwQcfUKdOHZo3b05GRgaTJ09m2rRpTJs2rWyvRK6ctV/mNYWsGW9eN78NHZ4yI1oOLTPrFvwDvALhdKxJk97ucbPcZoNb/l308YObaZSLiEglVuJg5L777uPkyZO8+eabxMbG0qJFC+bMmUPt2rUBiI2NzZdzJCMjg1GjRnHkyBE8PT1p3rw5s2fPpl+/fmV3FVK2TuyGlR+bqepDWsG8l83y9k/BmWOmn8Yfr0PN682cLjlWfJTXYbXnS6ZWRERE5BKUDl7y2LNNELLgX5Cdnn9dgxvNCBcwzTL7/zQJyLLSoHpTM2nchm/M+uAW8NclZl4WERGptK5oOni5Rv3+Isx/1QQidbqYIAPAuzrcMc40t9hsZlZbNx8TiIDJ+3Hz21C9ifl80xsKREREpNhKNbRXrkGJ0aZvCJj+HZHn+nsc2w7e1cD3vNFSVcLNkN1fR5igpVEfE6Q8NheSjpg5X0RERIpJwUhllZYMPz0GQU3gxjdh2X9MptO6Xc3IlxxFBRaRj5r+JNUamEAETBIzz6pXvuwiInJNUTBSWa38GPbNN6+kw7DzV7O82wvFP0bNiCtTNhERqVQUjFxrLCuvpqIoKfF588YAbJ9h/q3TJS8hmYiIyFWiDqzXisw0+G00vF0bdswqfJucgVPL/gMZZyD0OrhzPNjO/Rp0e/6qFFVEROR8qhm5FsTvg58GQdxW83npe9DsdvP+wGIzN0zMGjgTZ4bdHt9p1vV6xQzZ9Q02tSV1C58rSERE5EpSMFLRxe+DiTebSea8AiEtCWI3w/FdJkX75LtMx9QcsZvMv7U6Qf1e5n1hc7+IiIhcJQpGKrLEGDPrbcoJM6HcQz/Br8/C7jmwZSqcTTSBSM120OtVM2Fd7CaI3wvXPXTpviUiIiJXgYKRiip2C/wwAJIPQ2BDeHgG+FSHVveZYGTjt5CWaLa98Y28jqmB9R1WZBERkcIoGKloLAvWfAHzXoLsDDMh3YCZJhABM6uuuz+kHDefa3XUCBkRESnXNJqmPLIs2DgZdv6SNwImx/bp8NvfTSDSqC88uQj8a+atd/WA5v3zPncddTVKLCIiUmoKRsqjZe/Dz0Ph+4dh2mDTKRUgOwsWvmXedxwGD3wH3oEF948YCDZnCO+Q10lVRESknFIzTXmzfSb8+aZ5b3OCbT/BkXXw4I9wZD2c3AeeAdD9haI7oNaIgKfXmQnu1ElVRETKOQUj5UXmWVg3MS8QaT8EWtwNPz0Opw7CV73B1dus6/wMuPte/HgB9a5ocUVERMqKghFHS0uCDZNgxccmKRlAwz7Q5y1wcoYnF8K398DRDXD2FHhVg+ufcGyZRUREypCCEUfJSocl78GqTyHjtFnmHw5dnoM2D5tABMC7Ggz6FX4cBHvnQc+Xwc3bYcUWEREpawpGHOHoJpgxBE6cS8terTF0HAqtHwAXt4Lbu3nDgz/AmWPgG3JViyoiInKlKRi52jZMMllS7VmmyaXf/0Gz/uB0iYFNNpsCERERuSYpGLla7Hb44zVY8aH53ORWuO2/phlGRESkElMwcrWsm5AXiHR74eJDc0VERCoRBSNXg91uOqoC3Pg63PCsQ4sjIiJSnigD69UQtRgS9oObL7TTsFwREZHzKRi5Eux2mPO8SeWemgBrvzTLr3sA3H0cWzYREZFyRs00V8Ly/8Caz837oxsh4YB5H/m448okIiJSTikYKWuHVsKCf5n37v5mLhmAOl0gqInjyiUiIlJOqZmmLB3bAdMeBysbWt0Hf1thEpoBdPibY8smIiJSTqlmpCzYs2H5f2HRWMjOgMAGcMu/zWR2f11sakdCWjq6lCIiIuWSakbKwsqP4c83TCDS6GYYNCdvVl1XTwUiIiIiF6Gakctlt8PaCeZ9r1fhhpFKZiYiIlICqhm5XIeWQ+IhcPeD9k8pEBERESkhBSOXa9O35t/md4Kbl2PLIiIiUgEpGLkc6adhx8/mfZuHHVsWERGRCkp9RkrDboeUE7B5CmSmQmBDqNnO0aUSERGpkBSMlNTBZTBruJlrJkebh9RXREREpJQUjBSXPRvmvQyrxp1bYAPfEAhqCm0HOrRoIiIiFZmCkeLa/F1eIBIxCG76B3j4ObRIIiIi1wIFI8WVk0ukx0vQ7XnHlkVEROQaotE0xXF0ExzdAM5uEPmYo0sjIiJyTVEwUhzrJ5p/m94O3tUcWxYREZFrjIKRS0lLhi0/mveRjzq2LCIiItcg9RkpyvpvYMM3cOYEZKZAtUZQu7OjSyUiInLNUTBSmF2z4Zfh+Zd1+JtyiYiIiFwBCkYudHI/zHjKvG87AFrdDz5BENjAseUSERG5RikYOV9WBvwwENKTILwD3PI+OLs6ulQiIiLXNHVgPd+mb+HYVvAKhHu+ViAiIiJyFSgYyZGVDkveM++7Pg9+oY4tj4iISCWhYCTHhkmQfBh8Q026dxEREbkqFIwAZKbB0n+b912eA1cPx5ZHRESkElEwArD6MzgdC341zQgaERERuWoUjCQfhcXvmvc9XwYXd8eWR0REpJJRMDLvFZNhNbw9tLrP0aURERGpdCp3MHJwOWz7CbBB33fBqXL/OERERByh8j59LQvmvmDeRz4KYdc5tDgiIiKVVamCkXHjxlG3bl08PDyIiIhg6dKlF91+8eLFRERE4OHhQb169fjss89KVdgyZbNB/0+h8S3Q8xVHl0ZERKTSKnEw8v333zNixAheeuklNm7cSJcuXejbty/R0dGFbh8VFUW/fv3o0qULGzdu5MUXX2T48OFMmzbtsgt/2UJawANTwCvA0SURERGptGyWZVkl2aF9+/a0bduWTz/9NHdZ06ZN6d+/P2PHji2w/ejRo5k1axY7d+7MXTZkyBA2b97MypUri3XO5ORk/P39SUpKws/PryTFFREREQcp7vO7RDUjGRkZrF+/nt69e+db3rt3b1asWFHoPitXriywfZ8+fVi3bh2ZmZmF7pOenk5ycnK+l4iIiFybShSMxMfHk52dTXBwcL7lwcHBxMXFFbpPXFxcodtnZWURHx9f6D5jx47F398/9xUeHl6SYoqIiEgFUqoOrDabLd9ny7IKLLvU9oUtzzFmzBiSkpJyXzExMaUppoiIiFQALiXZuFq1ajg7OxeoBTl+/HiB2o8cISEhhW7v4uJCYGBgofu4u7vj7q5MqCIiIpVBiWpG3NzciIiIYP78+fmWz58/n06dOhW6T8eOHQtsP2/ePCIjI3F1dS1hcUVERORaU+JmmpEjR/Lll1/y1VdfsXPnTp599lmio6MZMmQIYJpYBgzIm2xuyJAhHDp0iJEjR7Jz506++uorJkyYwKhRo8ruKkRERKTCKlEzDcB9993HyZMnefPNN4mNjaVFixbMmTOH2rVrAxAbG5sv50jdunWZM2cOzz77LJ988glhYWF8+OGH3H333WV3FSIiIlJhlTjPiCMoz4iIiEjFc0XyjIiIiIiUNQUjIiIi4lAKRkRERMShFIyIiIiIQ5V4NI0j5PSx1Rw1IiIiFUfOc/tSY2UqRDBy+vRpAM1RIyIiUgGdPn0af3//ItdXiKG9drudo0eP4uvre9E5cEoqOTmZ8PBwYmJirtkhw7rGiu9avz7QNV4LrvXrA11jaViWxenTpwkLC8PJqeieIRWiZsTJyYmaNWteseP7+flds79YOXSNFd+1fn2ga7wWXOvXB7rGkrpYjUgOdWAVERERh1IwIiIiIg5VqYMRd3d3XnvtNdzd3R1dlCtG11jxXevXB7rGa8G1fn2ga7ySKkQHVhEREbl2VeqaEREREXE8BSMiIiLiUApGRERExKEUjIiIiIhDVepgZNy4cdStWxcPDw8iIiJYunSpo4tUKmPHjqVdu3b4+voSFBRE//792b17d75tBg0ahM1my/fq0KGDg0pccq+//nqB8oeEhOSutyyL119/nbCwMDw9PenevTvbt293YIlLrk6dOgWu0WazMXToUKDi3cMlS5Zw2223ERYWhs1mY+bMmfnWF+eepaen8/TTT1OtWjW8vb25/fbbOXz48FW8iou72DVmZmYyevRoWrZsibe3N2FhYQwYMICjR4/mO0b37t0L3Nf777//Kl9J0S51H4vze1me7+Olrq+wv0mbzcb//d//5W5Tnu9hcZ4P5eFvsdIGI99//z0jRozgpZdeYuPGjXTp0oW+ffsSHR3t6KKV2OLFixk6dCirVq1i/vz5ZGVl0bt3b1JSUvJtd/PNNxMbG5v7mjNnjoNKXDrNmzfPV/6tW7fmrnv33Xd5//33+fjjj1m7di0hISHcdNNNufMaVQRr167Nd33z588H4J577sndpiLdw5SUFFq3bs3HH39c6Pri3LMRI0YwY8YMpk6dyrJlyzhz5gy33nor2dnZV+syLupi15iamsqGDRt45ZVX2LBhA9OnT2fPnj3cfvvtBbZ94okn8t3Xzz///GoUv1gudR/h0r+X5fk+Xur6zr+u2NhYvvrqK2w2G3fffXe+7crrPSzO86Fc/C1aldT1119vDRkyJN+yJk2aWC+88IKDSlR2jh8/bgHW4sWLc5cNHDjQuuOOOxxXqMv02muvWa1bty50nd1ut0JCQqy33347d1laWprl7+9vffbZZ1ephGXvmWeeserXr2/Z7XbLsir2PQSsGTNm5H4uzj1LTEy0XF1dralTp+Zuc+TIEcvJycmaO3fuVSt7cV14jYVZs2aNBViHDh3KXdatWzfrmWeeubKFKyOFXeOlfi8r0n0szj284447rJ49e+ZbVpHu4YXPh/Lyt1gpa0YyMjJYv349vXv3zre8d+/erFixwkGlKjtJSUkABAQE5Fu+aNEigoKCaNSoEU888QTHjx93RPFKbe/evYSFhVG3bl3uv/9+Dhw4AEBUVBRxcXH57qe7uzvdunWrsPczIyODyZMn89hjj+WbHLKi38Mcxbln69evJzMzM982YWFhtGjRosLe16SkJGw2G1WqVMm3/Ntvv6VatWo0b96cUaNGVagaPbj47+W1dB+PHTvG7Nmzefzxxwusqyj38MLnQ3n5W6wQE+WVtfj4eLKzswkODs63PDg4mLi4OAeVqmxYlsXIkSO54YYbaNGiRe7yvn37cs8991C7dm2ioqJ45ZVX6NmzJ+vXr68Q2QTbt2/PpEmTaNSoEceOHeOf//wnnTp1Yvv27bn3rLD7eejQIUcU97LNnDmTxMREBg0alLusot/D8xXnnsXFxeHm5kbVqlULbFMR/07T0tJ44YUXePDBB/NNQPbQQw9Rt25dQkJC2LZtG2PGjGHz5s25zXTl3aV+L6+l+/jNN9/g6+vLXXfdlW95RbmHhT0fysvfYqUMRnKc/40TzI26cFlFM2zYMLZs2cKyZcvyLb/vvvty37do0YLIyEhq167N7NmzC/xhlUd9+/bNfd+yZUs6duxI/fr1+eabb3I7y11L93PChAn07duXsLCw3GUV/R4WpjT3rCLe18zMTO6//37sdjvjxo3Lt+6JJ57Ifd+iRQsaNmxIZGQkGzZsoG3btle7qCVW2t/Lingfv/rqKx566CE8PDzyLa8o97Co5wM4/m+xUjbTVKtWDWdn5wIR3fHjxwtEhxXJ008/zaxZs1i4cCE1a9a86LahoaHUrl2bvXv3XqXSlS1vb29atmzJ3r17c0fVXCv389ChQ/zxxx8MHjz4ottV5HtYnHsWEhJCRkYGp06dKnKbiiAzM5N7772XqKgo5s+ff8lp2du2bYurq2uFvK9Q8PfyWrmPS5cuZffu3Zf8u4TyeQ+Lej6Ul7/FShmMuLm5ERERUaAKbf78+XTq1MlBpSo9y7IYNmwY06dPZ8GCBdStW/eS+5w8eZKYmBhCQ0OvQgnLXnp6Ojt37iQ0NDS3evT8+5mRkcHixYsr5P2cOHEiQUFB3HLLLRfdriLfw+Lcs4iICFxdXfNtExsby7Zt2yrMfc0JRPbu3csff/xBYGDgJffZvn07mZmZFfK+QsHfy2vhPoKprYyIiKB169aX3LY83cNLPR/Kzd9imXSDrYCmTp1qubq6WhMmTLB27NhhjRgxwvL29rYOHjzo6KKV2FNPPWX5+/tbixYtsmJjY3NfqamplmVZ1unTp63nnnvOWrFihRUVFWUtXLjQ6tixo1WjRg0rOTnZwaUvnueee85atGiRdeDAAWvVqlXWrbfeavn6+uber7ffftvy9/e3pk+fbm3dutV64IEHrNDQ0ApzfTmys7OtWrVqWaNHj863vCLew9OnT1sbN260Nm7caAHW+++/b23cuDF3JElx7tmQIUOsmjVrWn/88Ye1YcMGq2fPnlbr1q2trKwsR11WPhe7xszMTOv222+3atasaW3atCnf32Z6erplWZa1b98+64033rDWrl1rRUVFWbNnz7aaNGlitWnTpkJcY3F/L8vzfbzU76llWVZSUpLl5eVlffrppwX2L+/38FLPB8sqH3+LlTYYsSzL+uSTT6zatWtbbm5uVtu2bfMNha1IgEJfEydOtCzLslJTU63evXtb1atXt1xdXa1atWpZAwcOtKKjox1b8BK47777rNDQUMvV1dUKCwuz7rrrLmv79u256+12u/Xaa69ZISEhlru7u9W1a1dr69atDixx6fz+++8WYO3evTvf8op4DxcuXFjo7+XAgQMtyyrePTt79qw1bNgwKyAgwPL09LRuvfXWcnXNF7vGqKioIv82Fy5caFmWZUVHR1tdu3a1AgICLDc3N6t+/frW8OHDrZMnTzr2ws5zsWss7u9leb6Pl/o9tSzL+vzzzy1PT08rMTGxwP7l/R5e6vlgWeXjb9F2rrAiIiIiDlEp+4yIiIhI+aFgRERERBxKwYiIiIg4lIIRERERcSgFIyIiIuJQCkZERETEoRSMiIiIiEMpGBERERGHUjAiIiIiDqVgRERERBxKwYiIiIg4lIIRERERcaj/B6X0TEEDx/jJAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.7098039215686275\n"
     ]
    }
   ],
   "source": [
    "# 导入必要的库\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms, models\n",
    "from tqdm import *\n",
    "import numpy as np\n",
    "import sys\n",
    "\n",
    "# 设备检测，若未检测到cuda设备则在CPU上运行\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "# 设置随机种子\n",
    "torch.manual_seed(0)\n",
    "\n",
    "# 定义模型、优化器、损失函数\n",
    "model = resnet18(num_classes=102).to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=0.002, momentum=0.9)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 设置训练集的数据变换，进行数据增强\n",
    "trainform_train = transforms.Compose([\n",
    "    transforms.RandomRotation(30), # 随机旋转 -30度到30度之间\n",
    "    transforms.RandomResizedCrop((224, 224)), # 随机比例裁剪并进行resize\n",
    "    transforms.RandomHorizontalFlip(p = 0.5), # 随机水平翻转\n",
    "    transforms.RandomVerticalFlip(p = 0.5), # 随机垂直翻转\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 设置测试集的数据变换，不进行数据增强，仅使用resize和归一化\n",
    "transform_test = transforms.Compose([\n",
    "    transforms.Resize((224, 224)),  # resize\n",
    "    transforms.ToTensor(),  # 将数据转换为张量\n",
    "    # 对三通道数据进行归一化(均值，标准差)，数值是从ImageNet数据集上的百万张图片中随机抽样计算得到\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
    "])\n",
    "\n",
    "# 加载训练数据，需要特别注意的是Flowers102数据集，test簇的数据量较多些，所以这里使用\"test\"作为训练集\n",
    "train_dataset = datasets.Flowers102(root='../data/flowers102', split=\"test\", download=True, transform=trainform_train)\n",
    "# 实例化训练数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)\n",
    "# 加载测试数据，使用\"train\"作为测试集\n",
    "test_dataset = datasets.Flowers102(root='../data/flowers102', split=\"train\", download=True, transform=transform_test)\n",
    "# 实例化测试数据加载器\n",
    "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)\n",
    "\n",
    "# 设置epoch数并开始训练\n",
    "num_epochs = 200  # 设置epoch数\n",
    "loss_history = []  # 创建损失历史记录列表\n",
    "acc_history = []   # 创建准确率历史记录列表\n",
    "\n",
    "# tqdm用于显示进度条并评估任务时间开销\n",
    "for epoch in tqdm(range(num_epochs), file=sys.stdout):\n",
    "    # 记录损失和预测正确数\n",
    "    total_loss = 0\n",
    "    total_correct = 0\n",
    "    \n",
    "    # 批量训练\n",
    "    model.train()\n",
    "    for inputs, labels in train_loader:\n",
    "        # 将数据转移到指定计算资源设备上\n",
    "        inputs = inputs.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        # 预测、损失函数、反向传播\n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        # 记录训练集loss\n",
    "        total_loss += loss.item()\n",
    "    \n",
    "    # 测试模型，不计算梯度\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        for inputs, labels in test_loader:\n",
    "            # 将数据转移到指定计算资源设备上\n",
    "            inputs = inputs.to(device)\n",
    "            labels = labels.to(device)\n",
    "            \n",
    "            # 预测\n",
    "            outputs = model(inputs)\n",
    "            # 记录测试集预测正确数\n",
    "            total_correct += (outputs.argmax(1) == labels).sum().item()\n",
    "        \n",
    "    # 记录训练集损失和测试集准确率\n",
    "    loss_history.append(np.log10(total_loss))  # 将损失加入损失历史记录列表，由于数值有时较大，这里取对数\n",
    "    acc_history.append(total_correct / len(test_dataset))# 将准确率加入准确率历史记录列表\n",
    "    \n",
    "    # 打印中间值\n",
    "    if epoch % 10 == 0:\n",
    "        tqdm.write(\"Epoch: {0} Loss: {1} Acc: {2}\".format(epoch, loss_history[-1], acc_history[-1]))\n",
    "\n",
    "# 使用Matplotlib绘制损失和准确率的曲线图\n",
    "import matplotlib.pyplot as plt\n",
    "plt.plot(loss_history, label='loss')\n",
    "plt.plot(acc_history, label='accuracy')\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# 输出准确率\n",
    "print(\"Accuracy:\", acc_history[-1])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
